package x.ovo.wechat.bot.plugin;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.db.nosql.redis.RedisDS;
import com.hankcs.hanlp.HanLP;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.thread.ThreadUtil;
import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.json.JSONUtil;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.resps.Tuple;
import x.ovo.wechat.bot.core.Constant;
import x.ovo.wechat.bot.core.Context;
import x.ovo.wechat.bot.core.contact.Contactable;
import x.ovo.wechat.bot.core.contact.RetrievalType;
import x.ovo.wechat.bot.core.enums.MessageType;
import x.ovo.wechat.bot.core.event.EventListener;
import x.ovo.wechat.bot.core.event.MessageEvent;
import x.ovo.wechat.bot.core.message.Message;
import x.ovo.wechat.bot.core.message.TextMessage;
import x.ovo.wechat.bot.core.plugin.Plugin;

import java.io.File;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@SuppressWarnings("DuplicatedCode")
@Slf4j(topic = "Statistics")
public class StatisticsListener extends EventListener<MessageEvent<Message>, Message> {

    private final RedisDS ds;
    private final Map<String, Set<String>> config = this.plugin.getConfig();

    private static final String TOTAL_COUNT_TEMPLATE = "statistics:total:count::{}";
    private static final String EVERDAY_COUNT_TEMPLATE = "statistics:everday:count::{}";
    private static final String EVERONE_COUNT_TEMPLATE = "statistics:everone:count::{}";
    private static final String TODAY_COUNT_TEMPLATE = "statistics:today:count::{}:{}";
    private static final String ANALYZE_TEMPLATE = "statistics:analyze::{}";
    private static final String TOKENIZER_KEY = "tokenizer::{}:{}";
    private static final List<String> KFC_ARGS = List.of("kfc", "肯德基", "v我50", "疯狂星期四");
    private static final List<String> KEYWORDS = List.of("今日排行", "昨日排行", "本周排行", "本月排行", "历史排行");
    private static final List<String> WORD_NATURE_PREFIX = List.of("n", "v", "a", "g");
    private static final String AI_ANALYZE_PREFIX = "qwen-innerGroupStatisticsAnalyze ";

    public StatisticsListener(Plugin plugin, RedisDS ds) {
        super(plugin);
        this.ds = ds;
    }

    @Override
    public boolean support(@NonNull MessageEvent<Message> messageMessageEvent, Message message) {
        // 支持所有消息事件，但消息只能是群消息且消息群成员不为null
        return message.isGroup() && Objects.nonNull(message.getMember()) && !message.getContent().startsWith(AI_ANALYZE_PREFIX);
    }

    @Override
    public boolean onEvent(@NonNull MessageEvent<Message> messageMessageEvent, Message message) {
        try {
            this.showAnalyze(message);
            this.statistics(message);
            this.insertDb(message);
            this.tokenizer(message);
            this.analyze(message);
        } catch (Exception e) {
            log.warn("[群消息统计] 统计失败");
        }
        return true;
    }

    private void tokenizer(Message message) {
        // 如果不是文本消息或是ai分析消息，不处理
        if (!MessageType.TEXT.equals(message.getType()) || message.getContent().startsWith(AI_ANALYZE_PREFIX)) return;

        String today = DateUtil.today();
        String key = StrUtil.format(TOKENIZER_KEY, message.getFrom().getNickName(), today);
        Jedis jedis = this.ds.getJedis();
        HanLP.segment(message.getContent().replaceAll(".*" + "<br/>- - - - - - - - - - - - - - -<br/>", ""))
                .stream()
                .filter(term -> term.word.length() > 1)
                .filter(term -> WORD_NATURE_PREFIX.stream().anyMatch(prefix -> term.nature.startsWith(prefix)))
                .forEach(term -> jedis.zincrby(key, 1, term.word));
        jedis.expire(key, TimeUnit.DAYS.toSeconds(31));
        log.debug("[{}] {} 发言分词统计完成", message.getFrom().getNickName(), message.getMember().getNickName());
        jedis.close();
    }

    private void showAnalyze(Message message) {
        // 判断是否文本消息
        if (message.getType() != MessageType.TEXT) return;
        boolean match = KEYWORDS.stream().noneMatch(key -> message.getContent().startsWith(key));
        if (match) return;
        // 获取发送消息的群成员昵称
        String member = message.getMember().getNickName();
        // 获取消息的群名称
        Contactable group = message.getFrom();
        // 判断该成员是否具有权限
        if (!this.plugin.getPluginDesc().getAuthors().contains(member) &&   // 不是作者
                !member.equals(Context.INSTANCE.getOwner().getNickName()) &&              // 不是bot所有者
                !(Objects.nonNull(this.config.get("*")) && this.config.get("*").contains(member)) &&    // 没有所有群的权限
                !(Objects.nonNull(this.config.get(group.getNickName())) && this.config.get(group.getNickName()).contains(member))   // 没有这个群的权限
        ) {
            return;
        }

        try {
            boolean all = message.getContent().contains("-a");
            String[] args = message.getContent().replace("-a", "").trim().split(" ");
            String option = args[0].trim();
            int top = args.length > 1 ? Integer.parseInt(args[1].trim()) : 10;
            String res = switch (option) {
                case "今日排行" -> this.showToDayRank(group, top, all);
                case "昨日排行" -> this.showYesterdayRank(group, top, all);
                case "本周排行" -> this.showThisWeekRank(group, top, all);
                case "本月排行" -> this.showThisMonthRank(group, top, all);
                case "历史排行" -> this.showHistoryRank(group, top, all);
                default -> null;
            };

            Optional.ofNullable(res).ifPresent(s -> {
                message.getFrom().sendMessage(s);
                message.getFrom().sendImage(FileUtil.file(this.plugin.getDataDir(), "wordcloud.png"));
            });
        } catch (Exception e) {
            log.warn("{}", e.getMessage(), e);
        }
        this.showAiAnalyze(message);

    }


    /**
     * 显示今日排名。
     * <p>
     * 此方法用于展示指定组别中，今日的排名情况。
     *
     * @param group 排名的组别，用于区分不同的排名群体。
     * @param top   需要展示的排名数量，用于控制显示的排名范围。
     */
    private String showToDayRank(Contactable group, int top, boolean showAllName) {
        String today = DateUtil.today();
        return getDayRank(group, top, showAllName, today, "今日");
    }

    /**
     * 显示昨日排名。
     * <p>
     * 该方法用于回顾昨天的排名情况，便于比较不同时期的排名变化。
     *
     * @param group 排名的组别。
     * @param top   需要展示的排名数量。
     */
    private String showYesterdayRank(Contactable group, int top, boolean showAllName) {
        String yesterday = DateUtil.format(DateUtil.yesterday(), "yyyy-MM-dd");
        return this.getDayRank(group, top, showAllName, yesterday, "昨日");
    }

    /**
     * 显示本周排名。
     * <p>
     * 该方法用于展示本周至今的排名情况，提供周度的排名视角。
     *
     * @param group 排名的组别。
     * @param top   需要展示的排名数量。
     */
    private String showThisWeekRank(Contactable group, int top, boolean showAllName) {
        // 获取本周日期
        List<DateTime> dateTimes = DateUtil.rangeToList(DateUtil.beginOfWeek(DateUtil.date()), DateUtil.endOfWeek(DateUtil.date()), DateField.DAY_OF_WEEK);
        return getRangeRank(dateTimes, group, top, showAllName, "本周");
    }

    /**
     * 显示本月排名。
     * <p>
     * 该方法用于展示本月至今的排名情况，提供月度的排名视角。
     *
     * @param group 排名的组别。
     * @param top   需要展示的排名数量。
     */
    private String showThisMonthRank(Contactable group, int top, boolean showAllName) {
        // 获取本月日期
        List<DateTime> dateTimes = DateUtil.rangeToList(DateUtil.beginOfMonth(DateUtil.date()), DateUtil.endOfMonth(DateUtil.date()), DateField.DAY_OF_MONTH);
        return getRangeRank(dateTimes, group, top, showAllName, "本月");
    }


    /**
     * 获取日排名
     *
     * @param group 群
     * @param top   top
     * @param day   日期
     * @param head  排行类型
     * @return {@link String }
     */
    private String getDayRank(Contactable group, int top, boolean showAllName, String day, String head) {
        String key = StrUtil.format(TODAY_COUNT_TEMPLATE, group.getNickName(), day);
        Jedis jedis = this.ds.getJedis();
        // 获取这个群今日消息总数
        String todayMessageCount = jedis.hget(StrUtil.format(EVERDAY_COUNT_TEMPLATE, group.getNickName()), day);
        if (Objects.isNull(todayMessageCount) || todayMessageCount.equals("0")) {
            jedis.close();
            return head + "好像死群了，没有统计到发言数据";
        }
        // 获取redis中今日统计的hash表数据，并按照value排序，取top
        Map<String, String> map = jedis.hgetAll(key);
        // 如果配置文件中配置了该群需要生成词云，则从redis中获取时间段内的分词统计数据并生成词云
        if (this.enabled("wordcloud", group.getNickName())) {
            // 获取分词统计数据生成词云
            key = StrUtil.format(TOKENIZER_KEY, group.getNickName(), day);
            List<Tuple> tuples = jedis.zrevrangeWithScores(key, 0, 99);
            WordCloudHrlper.generate(tuples, FileUtil.file(this.plugin.getDataDir(), "wordcloud.png"));
        }
        jedis.close();
        // 获取今日发言人数
        long talkerCount = getTalkerCount(map);
        // 获取排名，并获取群成员的群备注（若存在）、格式化
        AtomicInteger num = new AtomicInteger();
        String rank = map.entrySet().stream()
                .filter(Objects::nonNull)
                .filter(entry -> !entry.getKey().startsWith(ANALYZE_TEMPLATE.replace("{}", "")))
                .sorted(Comparator.<Map.Entry<String, String>>comparingInt(entry -> Integer.parseInt(entry.getValue())).reversed())
                .limit(top)
                .map(entry -> this.format(num, entry, group.getUserName(), showAllName))
                .collect(Collectors.joining("\n"));

        String analyzeResult = this.getAnalyzeResult(map);
        return buildResult(head, top, rank, talkerCount, todayMessageCount, analyzeResult);
    }

    /**
     * 获取范围排名
     *
     * @param dateTimes 日期时间
     * @param group     群
     * @param top       top
     * @param head      排行类型
     * @return {@link String }
     */
    private String getRangeRank(List<DateTime> dateTimes, Contactable group, int top, boolean showAllName, String head) {
        Jedis jedis = this.ds.getJedis();
        // 获取本周的消息总数
        Integer messageCount = dateTimes.stream()
                .map(date -> DateUtil.format(date, "yyyy-MM-dd"))
                .map(day -> jedis.hget(StrUtil.format(EVERDAY_COUNT_TEMPLATE, group.getNickName()), day))
                .filter(Objects::nonNull)
                .map(Integer::parseInt)
                .reduce(0, Integer::sum);
        if (messageCount == 0) {
            jedis.close();
            return head + "好像死群了，没有统计到发言数据";
        }
        // 如果配置文件中配置了该群需要生成词云，则从redis中获取时间段内的分词统计数据并生成词云
        if (this.enabled("wordcloud", group.getNickName())) {
            // 组装查询key，获取时间段内的分词统计数据并生成词云
            List<Tuple> tuples = dateTimes.stream()
                    .map(date -> DateUtil.format(date, "yyyy-MM-dd"))
                    .map(day -> StrUtil.format(TOKENIZER_KEY, group.getNickName(), day))
                    .map(key -> jedis.zrevrangeWithScores(key, 0, 99))
                    .flatMap(Collection::stream)
                    .toList();
            WordCloudHrlper.generate(tuples, FileUtil.file(this.plugin.getDataDir(), "wordcloud.png"));
        }

        // 组装查询key，从redis中查询数据并将每天的数据聚合
        Map<String, Integer> map = dateTimes.stream()
                .map(date -> DateUtil.format(date, "yyyy-MM-dd"))
                .map(day -> StrUtil.format(TODAY_COUNT_TEMPLATE, group.getNickName(), day))
                .map(jedis::hgetAll)
                .filter(Objects::nonNull)
                .flatMap(m -> m.entrySet().stream())
                .collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.summingInt(entry -> Integer.parseInt(entry.getValue()))));
        // 获取发言人数
        long talkerCount = getTalkerCount(map);
        jedis.close();
        // 获取排名，并获取群成员的群备注（若存在）、格式化
        AtomicInteger num = new AtomicInteger();
        String rank = map.entrySet().stream()
                .filter(Objects::nonNull)
                .filter(entry -> !entry.getKey().startsWith(ANALYZE_TEMPLATE.replace("{}", "")))
                .sorted(Comparator.<Map.Entry<String, Integer>>comparingInt(Map.Entry::getValue).reversed())
                .limit(top)
                .map(entry -> this.format(num, entry, group.getUserName(), showAllName))
                .collect(Collectors.joining("\n"));

        String analyzeResult = this.getAnalyzeResult(map);
        return buildResult(head, top, rank, talkerCount, messageCount, analyzeResult);
    }


    /**
     * 显示历史排名。
     * <p>
     * 该方法用于展示所有时间范围内的排名情况，不局限于特定的时间段。
     *
     * @param group 排名的组别。
     * @param top   需要展示的排名数量。
     */
    private String showHistoryRank(Contactable group, int top, boolean showAllName) {
        Jedis jedis = this.ds.getJedis();
        // 查询群历史消息总数
        long messageCount = Long.parseLong(jedis.get(StrUtil.format(TOTAL_COUNT_TEMPLATE, group.getNickName())));
        Map<String, String> map = jedis.hgetAll(StrUtil.format(EVERONE_COUNT_TEMPLATE, group.getNickName()));
        jedis.close();
        // 获取发言人数
        long talkerCount = getTalkerCount(map);
        // 查询群成员发言总数并排序、格式化
        AtomicInteger num = new AtomicInteger();
        String rank = map.entrySet().stream()
                .filter(Objects::nonNull)
                .filter(entry -> !entry.getKey().startsWith(ANALYZE_TEMPLATE.replace("{}", "")))
                .sorted(Comparator.<Map.Entry<String, String>>comparingInt(entry -> Integer.parseInt(entry.getValue())).reversed())
                .limit(top)
                .map(entry -> this.format(num, entry, group.getUserName(), showAllName))
                .collect(Collectors.joining("\n"));
        String analyzeResult = this.getAnalyzeResult(map);
        return buildResult("历史", top, rank, talkerCount, messageCount, analyzeResult);
    }

    /**
     * 统计群消息
     *
     * @param message 消息
     */
    private void statistics(Message message) {
        String group = message.getFrom().getNickName();
        String member = message.getMember().getNickName();

        String today = DateUtil.today();
        // 群消息总数+1
        String total_count = StrUtil.format(TOTAL_COUNT_TEMPLATE, group);
        Jedis jedis = this.ds.getJedis();
        jedis.incr(total_count);

        // 群每日消息总数+1
        String everday_count = StrUtil.format(EVERDAY_COUNT_TEMPLATE, group);
        jedis.hincrBy(everday_count, today, 1);

        // 群成员消息总数+1
        String everone_count = StrUtil.format(EVERONE_COUNT_TEMPLATE, group);
        jedis.hincrBy(everone_count, member, 1);

        // 群成员今日消息总数+1
        String today_count = StrUtil.format(TODAY_COUNT_TEMPLATE, group, today);
        jedis.hincrBy(today_count, member, 1);
        jedis.expire(today_count, TimeUnit.DAYS.toSeconds(31));

        jedis.disconnect();
        jedis.close();
        log.debug("[{}] 群消息统计完成", group);
    }

    /**
     * 分析群消息类型
     *
     * @param message 消息
     */
    private void analyze(Message message) {
        String group = message.getFrom().getNickName();
        String today = DateUtil.today();

        String key = StrUtil.format(ANALYZE_TEMPLATE, message.getType());
        // 统计今日的消息类型
        Jedis jedis = this.ds.getJedis();
        jedis.hincrBy(StrUtil.format(TODAY_COUNT_TEMPLATE, group, today), key, 1);
        jedis.hincrBy(StrUtil.format(EVERONE_COUNT_TEMPLATE, group), key, 1);

        // 分析是否是疯四文案并记录
        if (message instanceof TextMessage) {
            String content = message.getContent();
            KFC_ARGS.forEach(arg -> {
                if (content.contains(arg)) {
                    jedis.hincrBy(StrUtil.format(TODAY_COUNT_TEMPLATE, group, today), StrUtil.format(ANALYZE_TEMPLATE, "KFC"), 1);
                    jedis.hincrBy(StrUtil.format(EVERONE_COUNT_TEMPLATE, group), StrUtil.format(ANALYZE_TEMPLATE, "KFC"), 1);
                }
            });
        }
        jedis.close();
        log.debug("[{}] 群消息类型统计完成", group);
    }

    /**
     * 将聊天数据插入数据库
     *
     * @param message 消息
     */
    private void insertDb(Message message) {
        // 如果配置文件中不包含该群，则不记录
        if (!this.enabled("ai-analyze", message.getFrom().getNickName()) || message.getContent().startsWith(AI_ANALYZE_PREFIX))
            return;
        Statistics statistics = Statistics.builder()
                .from(message.getFrom().getNickName())
                .to(message.getTo().getNickName())
                .member(Optional.ofNullable(message.getMember()).map(m -> StrUtil.blankToDefault(m.getDisplayName(), m.getNickName())).orElse(""))
                .content(message.getContent())
                .type(message.getType().name())
                .time(DateUtil.now())
                .build();
        try {
            Db.use().insert(Entity.parse(statistics));
        } catch (Exception e) {
            log.error("插入数据库失败", e);
        }
    }

    /**
     * 显示 AI 分析
     *
     * @param message 消息
     */
    private void showAiAnalyze(Message message) {
        // 从数据库中获取数据
        if (!this.enabled("ai-analyze", message.getFrom().getNickName()) || !message.getContent().startsWith("今日排行"))
            return;
        Entity entity = Entity.create("statistics").set("from", message.getFrom().getNickName()).set("time", "like " + DateUtil.today() + "%");
        try {
            String s = Db.use().find(entity).stream()
                    .map(JSONUtil::toJsonStr)
                    .collect(Collectors.joining("\n"));

            // 写入文件
            File file = FileUtil.file(this.plugin.getDataDir(), "record.txt");
            FileUtil.writeUtf8String(s, file);

            Message clone = ObjUtil.clone(message);
            clone.setContent(AI_ANALYZE_PREFIX + "inner::file://" + file.getAbsoluteFile());
            ThreadUtil.execAsync(() -> new MessageEvent<>(clone).fire());
        } catch (Exception e) {
            log.error("获取ai总结失败：{}", e.getMessage());
        }
    }

    /**
     * 获取讲话者计数
     *
     * @param map 地图
     * @return long
     */
    private static long getTalkerCount(Map<String, ?> map) {
        // 获取这个群今日发言人数
        return map.entrySet().stream()
                // 出掉统计消息类型的key
                .filter(entry -> !entry.getKey().startsWith(ANALYZE_TEMPLATE.replace("{}", "")))
                .count();
    }

    /**
     * 获取分析结果，即文本、图像、视频等消息条数
     *
     * @param map map
     * @return {@link String }
     */
    private String getAnalyzeResult(Map<String, ?> map) {
        String prefix = ANALYZE_TEMPLATE.replace("{}", "");
        String string = map.entrySet().stream()
                .filter(entry -> entry.getKey().startsWith(prefix) && !entry.getKey().contains("KFC"))
                .map(entry -> StrUtil.format("{} {} 条", MessageType.valueOf(entry.getKey().replace(prefix, "")).getName(), entry.getValue()))
                .collect(Collectors.joining("，"));
        Object count = map.get(StrUtil.format(ANALYZE_TEMPLATE, "KFC"));
        return string + (Objects.isNull(count) ? "" : StrUtil.format("，疯狂星期四文案 {} 条", count));
    }

    private String format(AtomicInteger num, Map.Entry<String, ?> entry, String groupId, boolean showAllName) {
        String name = Optional.ofNullable(this.context.getContactManager().get(groupId, entry.getKey(), RetrievalType.NICK_NAME))
                .map(member -> {
                    if (showAllName) {
                        return member.getNickName() + (StrUtil.isBlank(member.getDisplayName()) ? "" : "<" + member.getDisplayName() + ">");
                    }
                    return StrUtil.isBlank(member.getDisplayName()) ? member.getNickName() : member.getDisplayName();
                })
                .orElse(entry.getKey());
        return StrUtil.format("[{} {}] {}", getEmoji(num.incrementAndGet()), String.format("%3s", entry.getValue()), name);
    }

    private static String getEmoji(int num) {
        return switch (num) {
            case 1 -> "🥇";
            case 2 -> "🥈";
            case 3 -> "🥉";
            case 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 -> String.valueOf((char) (9351 + num));
            default -> String.valueOf(num);
        };
    }

    private static String buildResult(String head, int top, String rank, long talkerCount, Object messageCount, String analyzeResult) {
        return StrUtil.format("{}排行前 {} :", head, top) +
                Constant.delimiter +
                rank +
                Constant.delimiter +
                StrUtil.format("共 {} 人发言，{} 条消息", talkerCount, messageCount) +
                Constant.delimiter +
                analyzeResult;
    }

    private boolean enabled(String key, String group) {
        List<String> list = this.plugin.<List<String>>getConfig().getOrDefault(key, Collections.emptyList());
        return CollUtil.isNotEmpty(list) && ("*".equals(list.get(0)) || list.contains(group));
    }
}